/**
 * This class aims to analyze the syntax of the dot language.  
 * @author Lucas Venezian Povoa <lucasvenez@gmail.com>
 */
function Parser() {
	
	var DEBUG = false;
	
	var VALID_CHARACTERS = 
		/([0-9]|[a-z]|[A-Z]|"|'|;|,|\[|\]|-|=|<|>|_|\s|\r|\t|\*|\/|\{|\})/;
	
	var graph = new Graph();
	
	/*
	 * Elements names
	 */
	var COMMA         = 01, 
	    ID            = 02, 
	    SEMICOLON     = 03, 
	    LEFT_KEY      = 04, 
	    RIGHT_KEY     = 05,
		LEFT_BRACE    = 06, 
		RIGHT_BRACE   = 07, 
		STRICT        = 08, 
		GRAPH         = 09,
		DIGRAPH       = 10, 
		SUBGRAPH      = 11, 
		SIMPLE_EDGE   = 12, 
		DIRECTED_EDGE = 13,
		EDGE          = 14, 
		NODE          = 15, 
		COLON         = 16, 
		EQUALS        = 17,
		N             = 18,
		NE            = 19,
		E             = 20,
		SE            = 21,
		S             = 22,
		SW            = 23,
		W             = 24,
		NW            = 25,
		C             = 26,
		_             = 27;
	
	var that = this;
	
	/**
	 * @param dotScript (String) is a script in dot language.
	 * @returns (QuadCollection) a collection that contains an element 
	 * with its type and line and column location.
	 */
	this.parse = function(dotScript) {
		
		var symbols = that.listGenerator(dotScript);
		
		if (that.checkGraphConstruction(symbols))
			return graph;
	};
	
	/**
	 * @param dotScript (String) is a script in dot language.
	 * @returns a QuadCollection structure based on dotScript. 
	 */
	this.listGenerator = function(dotScript) {

		var symbols = new Array();
		var lines   = new Array();
		var columns = new Array();
		
		var line = 1;
		var column = 1;

		var symbol = "";

		for ( var i = 0; i < dotScript.length; i++) {

			var letter = new String(dotScript.charAt(i));

			if (letter == "\"" || letter == "'") {
				
				var string_delimiter = letter;
				
				symbol += dotScript.charAt(i++);
				
				while (dotScript.charAt(i) != string_delimiter || 
						(dotScript.charAt(i-1) == "\\" && 
							dotScript.charAt(i) == string_delimiter)) {

					column++;
					
					if (dotScript.charAt(i) == "\n")
						throw "Invalid string at line " + line + " and column " + column;
					
					symbol += dotScript.charAt(i++);
				}
				
				symbol += dotScript.charAt(i);
				
			} else if (letter == "/") {

				if (dotScript.charAt(i + 1) == "/") {
					
					i++;

					while (dotScript.charAt(i).search(/(\n|\r\n)/) != 0) i++;

					line++;
					column = 0;
					
				} else if (dotScript.charAt(i + 1) == "*") {

					i++;
					column += 2;
					
					while (!(dotScript.charAt(i) == "*" && dotScript.charAt(i + 1) == "/")) {
						
						if (dotScript.charAt(i) == "\n") {
							line++;
							column = 1;
						} else
							column++;
						
						i++;
					}
				}
			} if (letter.search(VALID_CHARACTERS) != 0){
				
				throw LexicalException(letter, dotScript.charAt(i+1), line, column);
				
			} else if (letter.search(/^( |\r|\n|\t|}|{|;|,|\[|\]|=)/g) == 0) {

				if (symbol != "") {
					symbols.push(symbol);
					lines.push(line);
					columns.push(column - symbol.length);
					symbol = "";
				}

				if (letter.search(/^(}|{|;|,|\[|\]|=)/g) == 0) {
					
					symbols.push(letter + "");
					
					lines.push(line);
					
					columns.push(column);
				}

			} else if (letter == "-"
					&& (dotScript.charAt(i + 1) == ">" || dotScript
							.charAt(i + 1) == "-")) {

				if (symbol != "") {
					symbols.push(symbol);
					symbol = "";
				}

				if (dotScript.charAt(i + 1) == ">")
					symbols.push("->");
				else
					symbols.push("--");
				
				lines.push(line);
				
				columns.push(column);
				
				i++;

			} else
				symbol = symbol + letter;
			
			if (letter == "\n") {
				
				column = 1;
				line++;
			
			} else
				column++;
		}

		if (symbol != null && symbol != "" && symbol != undefined)
			symbols.push(symbol);
		
		var quad = new QuadCollection();

		for (var j = 0; j < symbols.length; j++) {

			var e = symbols[j];
			var t = 0;

			if (e == ";")
				t = SEMICOLON;

			else if (e == ",")
				t = COMMA;

			else if (e == ":")
				t = COLON;

			else if (e.toLowerCase() == "strict")
				t = STRICT;

			else if (e.toLowerCase() == "graph")
				t = GRAPH;

			else if (e.toLowerCase() == "subgraph")
				t = SUBGRAPH;

			else if (e.toLowerCase() == "digraph")
				t = DIGRAPH;

			else if (e.toLowerCase() == "node")
				t = NODE;
			
			else if (e.toLowerCase() == "edge")
				t = EDGE;
			
			else if (e == "=")
				t = EQUALS;

			else if (e == "{")
				t = LEFT_KEY;

			else if (e == "}")
				t = RIGHT_KEY;

			else if (e == "[")
				t = LEFT_BRACE;

			else if (e == "]")
				t = RIGHT_BRACE;

			else if (e == "--")
				t = SIMPLE_EDGE;

			else if (e == "->")
				t = DIRECTED_EDGE;

			else if (e.toLowerCase() == "n")
				t = N;
			
			else if (e.toLowerCase() == "ne")
				t = NE;
			
			else if (e.toLowerCase() == "e")
				t = E;
			
			else if (e.toLowerCase() == "se")
				t = SE;
			
			else if (e.toLowerCase() == "sw")
				t = SW;
			
			else if (e.toLowerCase() == "w")
				t = W;
			
			else if (e.toLowerCase() == "nw")
				t = NW;
			
			else if (e == "_")
				t = _;
			
			else if (e.match(/([<.+>]|".+"|'.+'|.+)/) != null)
				t = ID;
			
			if ( t > 0)
				quad.add(e, t, lines[j], columns[j]);
		}

		return quad;
	};
	
	this.checkGraphConstruction = function(symbolsList) {
		
		if (DEBUG)
			alert("checkGraphConstruction: " + symbolsList.get(0)[0]);
		
		if (symbolsList instanceof QuadCollection && symbolsList.getSize() >= 3 ) {
			
			if (symbolsList.get(0)[1] == STRICT) {
				symbolsList.remove(0);
				graph.setStrict(true);
			}
			
			if (symbolsList.get(0)[1] == GRAPH || symbolsList.get(0)[1] == DIGRAPH) {
				
				var digraph = symbolsList.get(0)[1] != GRAPH;
				
				graph.setDigraph(digraph);
				
				symbolsList.remove(0);
				
				if (symbolsList.get(0)[1] == ID) {
					
					graph.setID(symbolsList.get(0)[0]);
					
					symbolsList.remove(0);
				}
					
				if (symbolsList.get(0)[1]  == LEFT_KEY) {
						
					symbolsList.remove(0);
					symbolsList = that.stmt_list(symbolsList, digraph);
						
					if (symbolsList.get(0)[1]  == RIGHT_KEY)
						return true;
					else
						throw SintacticException(symbolsList.get(0));
				} else
					throw SintacticException(symbolsList.get(0));
			} else
				throw SintacticException(symbolsList.get(0));
		}
		
		throw SintacticException(symbolsList.get(0));
	};
	
	this.stmt_list = function(symbolsList, digraph) {
		
		if (DEBUG)
			alert("stmt_list: " + symbolsList.get(0)[0]);
		
		if (symbolsList instanceof QuadCollection) {
			
			if (symbolsList.getSize() > 1) {
				
				if (symbolsList.get(0)[1] == NODE ||
						symbolsList.get(0)[1] == GRAPH ||
						symbolsList.get(0)[1] == EDGE ||
						symbolsList.get(0)[1] == ID ||
						symbolsList.get(0)[1] == SUBGRAPH ||
						symbolsList.get(0)[1] == LEFT_KEY) {
					
					symbolsList = that.stmt(symbolsList, digraph);
			
					if (symbolsList.get(0)[1] == SEMICOLON)
						symbolsList.remove(0);
			
					symbolsList = that.stmt_list(symbolsList, digraph);
				
				} else if (symbolsList.get(0)[1] != RIGHT_KEY)
					throw SintacticException(symbolsList.get(0));
			}
			
			return symbolsList;
		} else
			throw "Invalid parameter type.";
	};
	
	this.stmt = function(symbolsList, digraph) {
		
		if (DEBUG)
			alert("stmt: " + symbolsList.get(0)[0]);
		
		if (symbolsList.get(0)[1] == ID && 
				symbolsList.get(1)[1] == EQUALS && 
					symbolsList.get(2)[1] == ID )
		
			symbolsList.remove(0, 1, 2);
		
		else if (symbolsList.get(0)[1] == NODE || symbolsList.get(0)[1] == GRAPH || symbolsList.get(0)[1] == EDGE )
			symbolsList = that.attr_stmt(symbolsList);
		
		else if ((symbolsList.get(0)[1] == ID || 
					symbolsList.get(0)[1] == SUBGRAPH || 
						symbolsList.get(0)[1] == LEFT_KEY) && 
							((symbolsList.get(1)[1] == DIRECTED_EDGE || 
									symbolsList.get(1)[1] == SIMPLE_EDGE))) {
			
			if ((symbolsList.get(1)[1] == DIRECTED_EDGE && digraph) || 
					(symbolsList.get(1)[1] == SIMPLE_EDGE && !digraph))
				symbolsList = that.edge_stmt(symbolsList, digraph);
			else
				throw SintacticException(symbolsList.get(1));
			
		} else if (symbolsList.get(0)[1] == ID)
			symbolsList = that.node_stmt(symbolsList);
		
		else if (symbolsList.get(0)[1] == SUBGRAPH || symbolsList.get(0)[1] == LEFT_KEY)
			symbolsList = that.subgraph(symbolsList, digraph);
		
		else
			throw SintacticException(symbolsList.get(0));
		
		return symbolsList;
		
	};
	
	this.attr_stmt = function(symbolsList) {
		
		if (DEBUG)
			alert("attr_stmt: " + symbolsList.get(0)[0]);
		
		if (symbolsList.get(0)[1] == GRAPH || 
				symbolsList.get(0)[1] == NODE || 
					symbolsList.get(0)[1] == EDGE)
			symbolsList.remove(0);
		else
			throw SintacticException(symbolsList.get(0));
		
		if (symbolsList.get(0)[1] == LEFT_BRACE)
			symbolsList = that.attr_list(symbolsList);
		
		return symbolsList;
	};
	
	this.attr_list = function(symbolsList) {
		
		if (DEBUG)
			alert("attr_list: " + symbolsList.get(0)[0]);
		
		if (symbolsList.get(0)[1] == LEFT_BRACE) {
			
			symbolsList.remove(0);
			
			symbolsList = that.a_list(symbolsList);
			
			if (symbolsList.get(0)[1] == RIGHT_BRACE)
				symbolsList.remove(0);
			else
				throw SintacticException(symbolsList.get(0));
			
			if (symbolsList.get(0)[1] == LEFT_BRACE)
				symbolsList = that.attr_list(symbolsList);
			
		} else
			throw SintacticException(symbolsList.get(0));

		return symbolsList;
	};
	
	this.a_list = function(symbolsList) {
		
		if (DEBUG)
			alert("a_list: " + symbolsList.get(0)[0]);
		
		if (symbolsList.get(0)[1] == ID)
			symbolsList.remove(0);
		else
			throw SintacticException(symbolsList.get(0));
		
		if (symbolsList.get(0)[1] == EQUALS)
			symbolsList.remove(0);
		else
			throw SintacticException(symbolsList.get(0));
		
		if (symbolsList.get(0)[1] == ID)
			symbolsList.remove(0);
		else
			throw SintacticException(symbolsList.get(0));
		
		if (symbolsList.get(0)[1] == SEMICOLON || 
				symbolsList.get(0)[1] == COMMA)
			symbolsList.remove(0);
		
		if (symbolsList.getSize() > 3)
			if (symbolsList.get(0)[1] == ID && 
					symbolsList.get(1)[1] == EQUALS && 
						symbolsList.get(2)[1] == ID)
				symbolsList = that.a_list(symbolsList);
		
		return symbolsList;
	};
	
	this.edge_stmt = function(symbolsList, digraph) {
		
		if (DEBUG)
			alert("edge_stmt: " + symbolsList.get(0)[0]);
		
		if (symbolsList.get(0)[1] == ID) {
			
			symbolsList = that.node_id(symbolsList);
			
		} else if (symbolsList.get(0)[1] == SUBGRAPH || symbolsList.get(0)[1] == LEFT_KEY)
			symbolsList = that.subgraph(symbolsList, digraph);
		else
			throw SintacticException(symbolsList.get(0));
		
		symbolsList = that.edgeRHS(symbolsList, digraph);
		
		if (symbolsList.get(0)[1] == LEFT_BRACE)
			symbolsList = that.attr_list(symbolsList);
		
		return symbolsList;
	};
	
	this.edgeRHS = function(symbolsList, digraph) {
		
		if (DEBUG)
			alert("edgeRHS: " + symbolsList.get(0)[0]);
		
		if ((symbolsList.get(0)[1] == DIRECTED_EDGE && digraph) || 
				(symbolsList.get(0)[1] == SIMPLE_EDGE && !digraph)) {
			
			symbolsList.remove(0);
			
			if (symbolsList.get(0)[1] == ID)
				symbolsList = that.node_id(symbolsList);
			else if (symbolsList.get(0)[1] == SUBGRAPH || symbolsList.get(0)[1] == LEFT_KEY)
				symbolsList = that.subgraph(symbolsList, digraph);
			else
				throw SintacticException(symbolsList.get(0));
			
			if ((symbolsList.get(0)[1] == DIRECTED_EDGE && digraph) || 
				(symbolsList.get(0)[1] == SIMPLE_EDGE && !digraph))
				symbolsList = that.edgeRHS(symbolsList, digraph);
			
		} else
			throw SintacticException(symbolsList.get(0));
			
		return symbolsList;
	};
	
	this.node_stmt = function(symbolsList) {
		
		if (DEBUG)
			alert("node_stmt: " + symbolsList.get(0)[0]);
		
		if (symbolsList.get(1)[1] != DIRECTED_EDGE && 
				symbolsList.get(1)[1] != SIMPLE_EDGE) {
			
			symbolsList = that.node_id(symbolsList);
		
			if (symbolsList.get(0)[1] == LEFT_BRACE)
				symbolsList = that.attr_list(symbolsList);
		}
		
		return symbolsList;
	};
	
	this.node_id = function(symbolsList) {
		
		if (DEBUG)
			alert("node_id: " + symbolsList.get(0)[0]);
		
		if (symbolsList.get(0)[1] == ID) {
			
			symbolsList.remove(0);
			
			if (symbolsList.get(0)[1] == COLON)
				symbolsList = that.port(symbolsList);
		} else
			throw SintacticException(symbolsList.get(0));
		
		return symbolsList;
	};
	
	this.port = function(symbolsList) {
		
		if (DEBUG)
			alert("port: " + symbolsList.get(0)[0]);
		
		if (symbolsList.get(0)[1] == COLON) {
			
			symbolsList.remove(0);
			
			if (symbolsList.get(0)[1] == ID) {
				
				symbolsList.remove(0);
				
				if (symbolsList.get(0)[1] == COLON) {
				
					symbolsList.remove(0);
					
					that.compass_pt(symbolsList);
				}
			} else
				that.compass_pt(symbolsList);
		} else
			throw SintacticException(symbolsList.get(0));
		
		return symbolsList;
	};
	
	this.subgraph = function(symbolsList, digraph) {
		
		if (DEBUG)
			alert("subgraph: " + symbolsList.get(0)[0]);
		
		if (symbolsList.get(0)[1] == SUBGRAPH) {
			
			symbolsList.remove(0);
			
			if (symbolsList.get(0)[1] == ID)
				symbolsList.remove(0);
		}
		
		if (symbolsList.get(0)[1] == LEFT_KEY) {
			
			symbolsList.remove(0);
			
			symbolsList = that.stmt_list(symbolsList, digraph);
			
			if (symbolsList.get(0)[1] == RIGHT_KEY) {
				symbolsList.remove(0);
			} else
				throw SintacticException(symbolsList.get(0));
			
		} else
			throw SintacticException(symbolsList.get(0));
		
		return symbolsList;
	};
	
	this.compass_pt = function(symbolsList) {
		
		if (DEBUG)
			alert("compass_pt: " + symbolsList.get(0)[0]);
		
		var e = symbolsList.get(0)[1];
		
		switch(e) {
			case N:  
			case NE: 
			case E:  
			case SE: 
			case S:  
			case SW: 
			case W:  
			case NW: 
			case C:  
			case _:
				symbolsList.remove(0);
				break;
			default:
				throw SintacticException(symbolsList.get(0));
		}
		
		return symbolsList;
	};
}